Basic Game Tutorial 4: Character Movement

Back again are we? Good to see you!

In this part of the Basic Game Tutorials, we'll be adding perhaps the most fun part of the project. How to move the character and jump around.

Sounds simple, right? Let's see about that shall we...

Actually, this part will be much more simple than the previous one. We have already completed our collision check and we already have gravity and velocity, so moving the character should be a walk in the park!

Before we actually animate the character, let's just get them moving around properly.

First, we'll tackle the left and right movement. Jumping will be more fun once we can move around a little!

We'll need two if statements to achieve left and right movement, one to check if the left directional button has been pressed, and one for the right directional button.

Before we go ahead and write the if statements, it would be very useful to have a variable to store the character's movement speed. Let's create that first. This will be a global variable at the top of the program. We'll add it at line 7, moving the gravity and velocity variables down a couple of lines:

  8. moveSpeed = 5
  9.
 10. gravity = 1
 11. velocity = 0

Done!

Using the Directional Buttons to Move the Character

Before we write the if statements to move the character, we must call the controls() function to access the Joy-Con buttons!

At the very start of the main loop, add line 34 below. We have included the surrounding lines to make it clear:

 31. loop
 32.     clear()
 33. 
 34.     c = controls(0)
 35.    
 36.     screenW = gwidth()
 37.     screenH = gheight()

Great! We now have access to all the Joy-Con buttons by using our c variable.

Now let's use the moveSpeed variable to create the movement if statements. We'll begin with a simple version, then we'll add some polish.

Add the following lines to your program:

 63.     if c.right then
 64.         playerX += moveSpeed
 65.     endif
 66.
 67.     if c.left then
 68.         playerX -= moveSpeed
 69.     endif

These two very simple if statements do almost everything for us. They allow us to press the left and right directional buttons and increase or decrease the player's x position.

However, run the program and you might notice that if you walk off an edge then quickly walk backwards, you can lodge yourself firmly inside a solid block! This isn't quite what we want.

We need to use our brilliant collision() function in these if statements to check if the tile we are about to move into is solid or empty.

Make the changes below to your if statements:

 63.     if c.right and !collide( playerX + tSize / 2 + moveSpeed, playerY + tSize - 1 ) then
 64.         playerX += moveSpeed
 65.     endif
 66.
 67.     if c.left and !collide( playerX + tSize / 2 - moveSpeed, playerY + tSize - 1 ) then
 68.         playerX -= moveSpeed
 69.     endif

Just like before, we are using the collision function to check if the position we are about to be in is solid or empty. This is why we must add or subtract moveSpeed depending on which way we are moving.

The Jump

Programming a jump is a key part of creating a platform game. There are multiple ways to achieve a good jump. Since we are already simulating gravity and velocity, our jump code will be very simple indeed and the results look fantastic!

This system of using gravity and velocity will work in any project you might want to use them in.

All we need to do is adjust the velocity variable. Let's add a very simple if statement to our program:

 54.     if c.a then
 55.         velocity -= 2
 56.     endif

Run the program and press the A button lightly. Hopefully the character will briefly jump into the air. When A is released, we will be brought back down to the ground by gravity. Awesome!

However... This jump is a little strange. We can hold down the A button to constantly move further into the air, which isn't exactly what we'd like! It's a bit silly really.

What we need is a timer which tracks how long we have been in the air, and stops us from holding down the A button to keep flying upwards if the timer reaches a certain point.

First, we'll need to create the variable at the start of our program:

 10. gravity = 1
 11. velocity = 0
 12.
 13. jumpTimer = 0

We've shown the gravity and velocity variables here to make it clear where to put the jumpTimer variable.

We begin the timer at 0 and we will count up as we are jumping. Let's add something to the jumping if statement:

 55.     if c.a and jumpTimer < 12 then
 57.         jumpTimer += 1
 58.         velocity -= 2
 59.     endif

That should do it! Run the program and press the A button to see our newly limited jump.

Oh dear! You might notice that we can only jump once!

This is because when we jump, we are now increasing the jumpTimer variable and it must be less than 12 if we want to jump again.

In order to jump again, we must reset the jumpTimer variable when we reach the ground.

In the if statement which checks to see if the character will collide the floor, we must add something after the else. Here's how the full if statement should look:

 63.     if !collision( playerX + tSize / 2, playerY + tSize + velocity ) then
 64.         playerY += velocity
 65.     else
 66.         playerY = int( ( playerY + velocity ) / tSize ) * tSize
 67.         velocity = 0
 68.         jumpTimer = 0
 69.     endif

Notice the new line on line 68. We reset the jumpTimer variable when we know the player has reached the floor.

A Little Extra Polish...

Our jump is pretty much complete. We can only stay in the air for a limited amount of time, we come wonderfully back down to the ground and we collide with the floor. This is really all we need, but it would be quite easy to add a couple of extra things which would really add some polish to our game.

When we jump in real life we have an initial burst of upward velocity, then as we spend more time in the air this decreases until it becomes negative, overcome by the force of gravity. We can make something very similar happen in our jump code with a very simple change:

 56.     if c.a and jumpTimer < 12 then
 57.         jumpTimer += 1
 58.         velocity -= 8 / jumpTimer
 59.     endif

We have changed line 58 so that our velocity is dependent on the jumpTimer variable. The longer we have been in the air, the more our velocity is divided by, therefore slowing our jump down as we reach the peak.

To make the jump work nicely we have also increased the amount we modify velocity by to 8. Changing this number will have a big effect on your jump!

You might have noticed that in the middle of your jump, you can press the A button again to pause slightly in the air. This looks a little silly and we can fix it with some very useful code. So let's do it!

We need to keep track of if the A button has been pressed. To do this we'll need a variable. Add the following global variable to the start of the program, just underneath the jumpTimer variable:

 13. jumpTimer = 0
 14. oldA = 0

This oldA variable will only be either true or false. We will store the old state of the A button in this variable, and check it against the current state of the A button using an if statement.

Let's put that idea to use:

 62.     if oldA and !c.a then
 63.         jumpTimer = 12
 64.     endif
 65.
 66.     oldA = c.a

This tricky if statement resets the jump timer to the maximum value if the A button is pressed again during the jump.

The Program So Far

As always, here is a copy of the entire program so far to make sure you're up to speed. If you're having problems with your code, feel free to start a new project and copy and paste the entire program to be certain:

  1. background = loadImage( "Kenney/backgrounds", false )
  2. tilesheet  = loadImage( "Kenney/superPlatformPack", false )
  3. chrSheet   = loadImage( "Kenney/characters", false )
  4.
  5. playerX = 0
  6. playerY = 0
  7.
  8. moveSpeed = 5
  9.
 10. gravity = 1
 11. velocity = 0
 12.
 13. jumpTimer = 0
 14. oldA = 0
 15.
 16. screenX = 0
 17. screenY = 0
 18.
 19. tiles = [ 121, 138, 128, 129, 130 ]
 20. 
 21. level = [
 22.     [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
 23.     [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
 24.     [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  2,  3,  4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
 25.     [  1,  1,  1,  1, -1, -1,  1,  1,  1,  1,  1,  1, -1, -1, -1, -1, -1,  1,  1,  1,  1,  1,  1,  1,  1,  1 ],
 26.     [  0,  0,  0,  0, -1, -1,  0,  0,  0,  0,  0,  0, -1, -1, -1, -1, -1,  0,  0,  0,  0,  0,  0,  0,  0,  0 ],
 27.     [  0,  0,  0,  0, -1, -1,  0,  0,  0,  0,  0,  0, -1, -1, -1, -1, -1,  0,  0,  0,  0,  0,  0,  0,  0,  0 ]
 28. ]
 29.
 30. levelHeight = 12
 31. levelOffset = levelHeight - len( level )
 32. tSize = 0
 33. 
 34. loop
 35.     clear()
 36.    
 37.     c = controls( 0 )
 38.
 39.     screenW = gwidth()
 40.     screenH = gheight()
 41.     scale = screenH / ( tileSize( tilesheet, 121 ).y * levelHeight )
 42.     tSize = scale * tileSize( tilesheet, 121 ).y
 43.     pSize = tileSize( chrSheet, 96 ) * scale
 44. 
 45.     drawImage( background, -screenX, -screenY, screenH / imageSize( background ).y )
 46.
 47.     for row = 0 to len( level ) loop
 48.         for col = 0 to len( level[0] ) loop
 49.             if level[row][col] >= 0 then
 50.                 x = col * tSize
 51.                 y = ( row + levelOffset ) * tSize
 52.                 drawSheet( tilesheet, tiles[level[row][col]], x, y, scale )
 53.             endif
 54.         repeat
 55.     repeat
 56.
 57.     if c.a and jumpTimer < 12 then
 58.         jumpTimer += 1
 59.         velocity -= 8 / jumpTimer
 60.     endif
 61.
 62.     if oldA and !c.a then
 63.         jumpTimer = 12
 64.     endif
 65.
 66.     oldA = c.a
 67.
 68.     velocity += gravity
 69.    
 70.     if !collision( playerX + pSize.x / 2, playerY + pSize.y + velocity ) then
 71.         playerY += velocity
 72.     else
 73.         playerY = int( ( playerY + velocity + pSize.y ) / tSize ) * tSize - pSize.y
 74.         velocity = 0
 75.         jumpTimer = 0
 76.     endif
 77.
 78.     if c.right and !collision( playerX + pSize.x / 2 + moveSpeed, playerY + pSize.y - 1 ) then
 79.         playerX += moveSpeed
 80.     endif
 81.
 82.     if c.left and !collision( playerX + pSize.x / 2 - moveSpeed, playerY + pSize.y - 1 ) then
 83.         playerX -= moveSpeed
 84.     endif
 85.    
 86.     drawSheet( chrSheet, 96, playerX, playerY, scale )
 87.
 88.     update()
 89. repeat
 90.
 91. function collision( x, y )
 92.     tileX = int( x / tSize )
 93.     tileY = int( y / tSize ) - levelOffset
 94.    
 95.     result = true
 96.    
 97.     if tileY < 0 or tileY >= len( level ) or tileX < 0 or tileX >= len( level[0] ) then
 98.         result = false
 99.     else
100.         if level[tileY][tileX] < 0 then
101.             result = false
102.         endif
103.     endif
104. return result

Functions and Keywords used in this tutorial

clear(), controls(), drawImage(), drawSheet(), else, endIf, for, function, gHeight(), gWidth(), if, int(), len(), loadImage(), loop, repeat, return, tileSize(), then, to, update()